1 Mapping in R

To be fair, cartography and spatial analysis is not one of my favorite things to do in R. While nearly every statistical analysis you want to do has several packages that can accomplish the same task, rarely do they rely one drastically different types of data. This is why a lot of spatial analysis is still performed in programs such as ArcGIS or QGIS. However, moving data from one program to another often requires reformatting the data which can also be cumbersome. So maintaining the data within a single program to manage, format, analyze, and visualize helps to create a usable, repeatable workflow. That’s why data analysis and visualization in R is becoming standard practice in many disciplines.

1.1 Data Types

When using R for spatial analysis there are a number of graphics packages that can be used for everything from basic mapping to interactive maps. A few of these packages you may already be familiar with such as plot from base R, ggplot2, plotly, leaflet. Many of these packages are dependent on other packages that help you manage, organize, and format spatial data. Packages such as sf “Simple Features for R”, sp “Classes and Methods for Spatial Data”, and rgdal “Bindings for the ‘Geospatial’ Data Abstraction Library” provide access to spatial data and spatial data formats for a number of other packages. In order to reduce the overall complexity of this exercise, we will not discuss all of the data formats or packages that can be used to analyze spatial data here. However, I will provide links to useful online resources in the exercise README document.

1.2 Packages

For this exercise we will use a few common packages for working with spatial data. Once again, many of these packages will require dependencies so installing them may take some time.

ggsn | leaflet | mapdata | maptools | OpenStreetMap | rgdal | smoothr | sf | sp | tidyverse | tmap

In order to avoid forcing these packages to your computer, search in the Packages Tab of RStudio to see if you need to install them. Once you have installed them you can continue with the script below:

packages<-c("ggsn","leaflet","mapdata","maptools","OpenStreetMap","rgdal","smoothr","sf","sp","tidyverse","tmap")
sapply(packages, require, character.only=T)
         ggsn       leaflet       mapdata      maptools OpenStreetMap         rgdal       smoothr            sf 
         TRUE          TRUE          TRUE          TRUE          TRUE          TRUE          TRUE          TRUE 
           sp     tidyverse          tmap 
         TRUE          TRUE          TRUE 

These packages will provide the minimum functionality required to complete this exercise. As you experiment with various styles of mapping you may require additional packages to be installed at a later time.

2 Simple Maps

There are a number of very simple maps you can create that uses data included in the package downloads. For this first example we are going to create a map that depicts the location of Austin Peay State University. To begin we need to load some data from ggplot2 using the map_data command.

state <- map_data("state")
county <- map_data("county")
apsu_point <- data.frame("x" = -87.353069, "y" = 36.533654)

This quick script loaded all of the state and county information for US into the appropriate objects and created a point centered on the APSU campus. However, we can create a better map by filtering the data to focus on Tennessee and Montgomery County.

tn <- county %>% 
  filter(region=="tennessee")

montco <- county %>% 
  filter(region=="tennessee") %>% 
  filter(subregion=="montgomery")

With just these simple lines of code and included datasets we can use ggplot2 to quickly create a simple locator map for campus.

ggplot() + geom_polygon(data = state, aes(x=long, y = lat, group = group),
                        fill = "white", color="black") + 
           geom_polygon(data = tn, aes(x=long, y = lat, group = group),
                        fill = "gray", color="black") + 
           geom_polygon(data = montco, aes(x=long, y = lat, group = group),
                        fill = "red", color="black") + 
           geom_point(data = apsu_point, aes(x=x,y=y), color="black") +
  coord_fixed(xlim = c(-91, -81),  ylim = c(34, 37), ratio = 1.2) + 
  xlab("Longitude") + ylab("Latitude") + ggtitle("APSU, Montgomery Co., TN")

Keep in mind, that when using ggplot2 the order of the geoms is important for proper display of the image. Using the various options within ggplot2 you can customize the map based on your preferences.

2.1 Maps with raster images

It’s likely that the map you need to create is slightly more complex than the one above. Maybe you are interested in using some sort of imagery of basemap to add complexity or provide additional information for a locations. In the data folder of this exercise is a *.csv file called campus_points. This file contains the location of several points on the main campus. In order to view these files on aerial imagery of campus we are going to use OpenStreetMap to obtain a basemap for our closer look at campus.

Note: As of 2018, Google began requiring an API key for use with get_map that allowed for Google imagery to be used as basemaps. Although you can obtain a certain number of maps free per month, I have chosen to avoid that set-up for this example.

To use OpenStreetMap you need to first obtain upper-left and lower-right coordinates for the location you are interested in displaying. This can be done through programs like Google Earth or through min() and max() commands using the information in your dataset.

CampusMap <- openmap(c(36.5360,-87.3570),c(36.5300,-87.3495), type='bing')

APSU <- openproj(CampusMap, projection = "+proj=longlat +ellps=WGS84 +units=m +no_defs")

In the script above the bounding box was identified, the type of map listed (view ?openmap for additional options), and we set the projection for the map. Refer to the lecture for information rearding the projection and ellipsoid.

To add this imagery to the map we need to use the autoplot function in ggplot2. This function extends the plotting functions to include some spatial data classes. Otherwise the script for creating the map is similar to the simple map above.

campus_points <- read.csv(file = "./Data/campus_points.csv")
autoplot(APSU) +
  geom_point(data=campus_points, aes(x = X, y = Y, color=Name), size = 4, alpha = 0.8) +
  geom_text(data=campus_points,aes(x=X,y=Y,label=Name), color="black", vjust=-0.60, size=4.01, fontface="bold") +
  geom_text(data=campus_points,aes(x=X,y=Y,label=Name), color="white", vjust=-0.75, fontface="bold") +
  labs(x="Longtiude", y="Latitude") + theme(legend.position = "none")

With the addition of the aerial photo there is additional spatial information that can be obtained from viewing the map. Similar to the simple map above, you can customize the map through setting various themes and aesthetics in ggplot2.

2.2 Using KMLs or Shapefiles

Occassionally you might receive information from a collaborator that is not a *.csv file. Depending on the individual, that information might be provided as a *.kml file from Google Earth or in an ESRI shapefile. Using a simple import command in the rgdal package, these files can also be used with ggplot2.

To read either shapefiles or KML files you can use the following syntax:

readOGR(dsn="path to the shapefile",layer="name of the shapefile")

or

readOGR("path to kml/name of the file.kml")

With this in mind, you can easily use Google Earth to create your own datasets and import them into R for spatial analysis and mapping. This can be useful when collaborating with individuals or with citizen science projects.

In the data folder of this exercise you will find Campus_Points.kml file and a Main_Campus.kml file. The campus points file includes the same data as the previous *.csv file but as a kml, and the main campus file contains a polygon outline of the main portion of campus. You can use the script above to create datasets out of those files.

Working in ggplot2 with SpatialPointsDataFrame or SpatialPolygonDataFrame files can be difficult due to the structure of the data types. So to avoid adding additional packages or detailed explanation for this exercise, we will convert the points and polygon to a data.frame and spatialpolygon respectively. In later exercises we will work directly with these more difficult spatial classes.

To convert the point kml to a data.frame:

campusOGR <- readOGR("./Data/Campus_Points.kml")
OGR data source with driver: KML 
Source: "C:\Users\gentryc\Google Drive\APSU\Courses\BIOL 5700 Advanced Data Analytics\Exercise_9\Data\Campus_Points.kml", layer: "Campus"
with 23 features
It has 2 fields
campus_points <- cbind(campusOGR@data,campusOGR@coords)
campus_points[2] <- NULL
campus_points[4] <- NULL
colnames(campus_points) <- c("Name","X","Y")

This allowed us to import the kml, create a data.frame out of the coordinate and names, removed unnecessary columns, and renamed the columns.

To convert the polygon kml to a spatial polygon:

outlineOGR <- readOGR("./Data/Main_Campus.kml")
OGR data source with driver: KML 
Source: "C:\Users\gentryc\Google Drive\APSU\Courses\BIOL 5700 Advanced Data Analytics\Exercise_9\Data\Main_Campus.kml", layer: "Main_Campus.kml"
with 1 features
It has 2 fields
outline_points <- outlineOGR@polygons[[1]]@Polygons[[1]]@coords
colnames(outline_points) <- c("X","Y")
outline <- Polygon(outline_points)
sp_outline <- Polygons(list(outline),1)
outline_poly <- SpatialPolygons(list(sp_outline))
proj4string(outline_poly) <- CRS("+proj=longlat +datum=WGS84 +ellps=WGS84")

This script created a spatial polygon and projected it to match the point data.

Now we can use these files to map the data from the kml files similar to the previous map of the csv data.

autoplot(APSU) +
  geom_polygon(data=outline_poly, aes(x=outline_poly@polygons[[1]]@Polygons[[1]]@coords[,1],
                                      y=outline_poly@polygons[[1]]@Polygons[[1]]@coords[,2]),
               alpha = .5, color="white") +
  geom_point(data=campus_points, aes(x = X, y = Y, color=Name), size = 4, alpha = 0.8) +
  geom_text(data=campus_points,aes(x=X,y=Y,label=Name), color="black", vjust=-0.60, size=4.01, fontface="bold") +
  geom_text(data=campus_points,aes(x=X,y=Y,label=Name), color="white", vjust=-0.75, fontface="bold") +
  labs(x="Longtiude", y="Latitude") + theme(legend.position = "none")

The script above is a little more confusing becase of the spatialpolygon class data. In order to map the aesthetics to the polygon we need to connect to the containers that hold the x and y values. This can be a confusing string, but we will discuss it further in upcoming exercises.

2.3 Student Submitted Mapping Question

How to create smooth polygons for species distribution maps from a set of points?

Following the “polygon kml to a spatial polygon” script above, install and load the smoothr package if you haven’t already. The smooth command in this package will take a polygon with geometric edges and “smooth” the data using one of the smoothing methods available in the package: chaikin, ksmooth, spline, or densify. Then you can adjust the smoothness value to determine how much smoothing is appropriate.

#Plot of the original polygon
plot(outline_poly)

#Smoothed version of the polygon
smooth_outline <- smooth(outline_poly, method="ksmooth", smoothness = 2)
plot(smooth_outline, col = alpha("grey", 0.2), border = "black", lwd = 1.5)

3 Interactive Maps

While there are a number of packages that can help create interactive maps such as plotly and mapview, for this example we are going to use leaflet. Leaflet allows you to create interactive web maps with the javaScript ‘Leaflet’ library. Because this package already has handlers for sf class data, we can use the original data we imported from the kml files to complete this portion.

In the most simple version, we set the point dataset we want to use for the map, tell leaflet the sort of markers we want to use to identify the points, and then let leaflet obtain the OSM tiles for the background image.

leaflet(campusOGR) %>% 
  addTiles() %>% 
  addMarkers(popup = campusOGR@data$Name)

Clicking on the markers will identify the place based on the “name” variable in our data. Because we have additional data from the polygon kml we can add to the map and customize it further.

leaflet(campusOGR) %>% 
  addTiles() %>%
  addPolygons(outlineOGR, 
              lng = outlineOGR@polygons[[1]]@Polygons[[1]]@coords[,1], 
              lat = outlineOGR@polygons[[1]]@Polygons[[1]]@coords[,2],
              fillColor = "grey",
              color = "black") %>%
  addCircleMarkers(popup = campusOGR@data$Name,
                   weight = 2,
                   color = "grey",
                   fillColor = "red",
                   fillOpacity = 0.7)

Here we customized the map with APSU colors and added the polygon to the background to identify the “main” campus area. We can take this a step further and provide additional information about the campus buildings and polygon by editing the attributes of each dataset.

outlineOGR@data$Description <- c("Austin Peay State University")

campusOGR@data$Description <- c("Academic", "Adminstrative", "Academic", "Library", "Academic", "Food", "Administrative", "Academic", "Residential", "Administrative", "Administrative", "Bookstore", "Academic", "Academic", "Residential", "Academic", "Academic", "Academic", "Academic", "Residential", "Residential", "Administrative", "Academic")

In the script above we provided a description of the polygon and descriptions of each building that can be used to identify the purpose of the building when the cursor hovers over the point.

leaflet(campusOGR) %>% 
  addTiles() %>%
  addPolygons(outlineOGR, 
              lng = outlineOGR@polygons[[1]]@Polygons[[1]]@coords[,1], 
              lat = outlineOGR@polygons[[1]]@Polygons[[1]]@coords[,2],
              label = outlineOGR@data$Description,
              fillColor = "grey",
              color = "black") %>%
  addCircleMarkers(popup = campusOGR@data$Name,
                   label = campusOGR@data$Description,
                   weight = 2,
                   color = "grey",
                   fillColor = "red",
                   fillOpacity = 0.7)

Notice now that when you move the cursor around there is additional information provided about each dataset. This can be used with numberous variable or to create an attribute table for each point when clicked.

3.1 Terrain and Elevation

Leaflet has a number of possible backgrounds that can be included as a basemap to your project. For example, if you were interested in elevation you can connect to a web-mapping service to obtain a zoomable terrain layer

leaflet() %>% 
  setView(lng = -112.956472, lat = 37.251343, zoom = 13) %>%
  addWMSTiles("https://basemap.nationalmap.gov/arcgis/services/USGSTopo/MapServer/WmsServer", layers = "0") %>%
  addMiniMap(zoomLevelOffset = -4) %>%
  addScaleBar()

3.2 Selectable Layers

Additionally, you can incorporate several basemaps and add an interactive selection tool to chose what background to display, set tranparencies for multiple backgrounds to be displayed at once, have live updates streamed to your map, or toggle the data off and on.

leaflet(campusOGR) %>% 
  addTiles(group = "OSM")%>%
  addProviderTiles(providers$CartoDB.Positron, group = "CartoDB") %>%
  #addProviderTiles(providers$Esri.NatGeoWorldMap) %>%
  addProviderTiles(providers$Esri.WorldImagery, group = "ESRI") %>%
  #addProviderTiles(providers$Stamen.Toner) %>%
    setView(lng = -87.353069, lat = 36.533654, zoom = 17) %>%
  addPolygons(outlineOGR, 
              lng = outlineOGR@polygons[[1]]@Polygons[[1]]@coords[,1], 
              lat = outlineOGR@polygons[[1]]@Polygons[[1]]@coords[,2],
              label = outlineOGR@data$Description,
              fillColor = "grey",
              color = "black") %>%
  addCircleMarkers(popup = campusOGR@data$Name,
                   label = campusOGR@data$Description,
                   group = "APSU",
                   weight = 2,
                   color = "grey",
                   fillColor = "red",
                   fillOpacity = 0.7) %>%
  addLayersControl(
    baseGroups = c("OSM", "ESRI", "CartoDB"),
    options = layersControlOptions(collapsed = FALSE),
    overlayGroups = "APSU")

This is only a very small fraction of the possible applications for mapping that can be done in R.

4 Your Turn!

Using the skills discussed in this exercise, create a site map for your thesis project or other dataset of interest. This map can be static or interactive, and can contain as many datasets or features as you feel are necessary to properly display your data. This map should be added to the project page you created in the last exercise to help build a “site description” section and/or “results” section if that data is currently available. Remember that this page should be consistently updated throughout the remainder of the semester culminating in your final presentation to the class in December. For an added challenge, you can try to fork this repository to give you access to the data files and a jumpstart on your script.

LS0tDQp0aXRsZTogTWFwcGluZyBCYXNpY3MgPGJyPjxzbWFsbD5BZHZhbmNlZCBEYXRhIEFuYWx5dGljczwvc21hbGw+PC9icj4NCmF1dGhvcjogQklPTCA1NzAwLCBGYWxsIDIwMTkNCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazoNCiAgICBkZl9wcmludDogcGFnZWQNCiAgICByb3dzLnByaW50OiAxMA0KICAgIHRoZW1lOiBjb3Ntbw0KICAgIGhpZ2hsaWdodDogYnJlZXplZGFyaw0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgdG9jOiB0cnVlDQogICAgdG9jX2Zsb2F0Og0KICAgICAgY29sbGFwc2VkOiBmYWxzZQ0KICAgICAgc21vb3RoX3Njcm9sbDogdHJ1ZQ0KICBwZGZfZG9jdW1lbnQ6IGRlZmF1bHQNCmVkaXRvcl9vcHRpb25zOiANCiAgY2h1bmtfb3V0cHV0X3R5cGU6IGlubGluZQ0KLS0tDQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KDQpoMS50aXRsZSB7DQogIGZvbnQtc2l6ZTogNDBweDsNCiAgZm9udC1mYW1pbHk6ICJUaW1lcyBOZXcgUm9tYW4iLCBUaW1lcywgc2VyaWY7DQogIGNvbG9yOiBEYXJrQmx1ZTsNCiAgdGV4dC1hbGlnbjogY2VudGVyOw0KfQ0KaDQuYXV0aG9yIHsgLyogSGVhZGVyIDQgLSBhbmQgdGhlIGF1dGhvciBhbmQgZGF0YSBoZWFkZXJzIHVzZSB0aGlzIHRvbyAgKi8NCiAgZm9udC1zaXplOiAyMHB4Ow0KICBmb250LWZhbWlseTogIlRpbWVzIE5ldyBSb21hbiIsIFRpbWVzLCBzZXJpZjsNCiAgY29sb3I6IERhcmtCbHVlOw0KICB0ZXh0LWFsaWduOiBjZW50ZXI7DQp9DQo8L3N0eWxlPg0KDQojIE1hcHBpbmcgaW4gUg0KVG8gYmUgZmFpciwgY2FydG9ncmFwaHkgYW5kIHNwYXRpYWwgYW5hbHlzaXMgaXMgbm90IG9uZSBvZiBteSBmYXZvcml0ZSB0aGluZ3MgdG8gZG8gaW4gKipSKiouIFdoaWxlIG5lYXJseSBldmVyeSBzdGF0aXN0aWNhbCBhbmFseXNpcyB5b3Ugd2FudCB0byBkbyBoYXMgc2V2ZXJhbCBwYWNrYWdlcyB0aGF0IGNhbiBhY2NvbXBsaXNoIHRoZSBzYW1lIHRhc2ssIHJhcmVseSBkbyB0aGV5IHJlbHkgb25lIGRyYXN0aWNhbGx5IGRpZmZlcmVudCB0eXBlcyBvZiBkYXRhLiBUaGlzIGlzIHdoeSBhIGxvdCBvZiBzcGF0aWFsIGFuYWx5c2lzIGlzIHN0aWxsIHBlcmZvcm1lZCBpbiBwcm9ncmFtcyBzdWNoIGFzICpBcmNHSVMqIG9yIFsqUUdJUypdKGh0dHBzOi8vcWdpcy5vcmcvZW4vc2l0ZS8pLiBIb3dldmVyLCBtb3ZpbmcgZGF0YSBmcm9tIG9uZSBwcm9ncmFtIHRvIGFub3RoZXIgb2Z0ZW4gcmVxdWlyZXMgcmVmb3JtYXR0aW5nIHRoZSBkYXRhIHdoaWNoIGNhbiBhbHNvIGJlIGN1bWJlcnNvbWUuIFNvIG1haW50YWluaW5nIHRoZSBkYXRhIHdpdGhpbiBhIHNpbmdsZSBwcm9ncmFtIHRvIG1hbmFnZSwgZm9ybWF0LCBhbmFseXplLCBhbmQgdmlzdWFsaXplIGhlbHBzIHRvIGNyZWF0ZSBhIHVzYWJsZSwgcmVwZWF0YWJsZSB3b3JrZmxvdy4gVGhhdCdzIHdoeSBkYXRhIGFuYWx5c2lzIGFuZCB2aXN1YWxpemF0aW9uIGluICoqUioqIGlzIGJlY29taW5nIHN0YW5kYXJkIHByYWN0aWNlIGluIG1hbnkgZGlzY2lwbGluZXMuDQoNCiMjIERhdGEgVHlwZXMNCldoZW4gdXNpbmcgKipSKiogZm9yIHNwYXRpYWwgYW5hbHlzaXMgdGhlcmUgYXJlIGEgbnVtYmVyIG9mIGdyYXBoaWNzIHBhY2thZ2VzIHRoYXQgY2FuIGJlIHVzZWQgZm9yIGV2ZXJ5dGhpbmcgZnJvbSBiYXNpYyBtYXBwaW5nIHRvIGludGVyYWN0aXZlIG1hcHMuIEEgZmV3IG9mIHRoZXNlIHBhY2thZ2VzIHlvdSBtYXkgYWxyZWFkeSBiZSBmYW1pbGlhciB3aXRoIHN1Y2ggYXMgYGBgcGxvdGBgYCBmcm9tICpiYXNlIFIqLCAgYGBgZ2dwbG90MmBgYCwgYGBgcGxvdGx5YGBgLCBgYGBsZWFmbGV0YGBgLiBNYW55IG9mIHRoZXNlIHBhY2thZ2VzIGFyZSBkZXBlbmRlbnQgb24gb3RoZXIgcGFja2FnZXMgdGhhdCBoZWxwIHlvdSBtYW5hZ2UsIG9yZ2FuaXplLCBhbmQgZm9ybWF0IHNwYXRpYWwgZGF0YS4gUGFja2FnZXMgc3VjaCBhcyBgYGBzZmBgYCAiU2ltcGxlIEZlYXR1cmVzIGZvciBSIiwgYGBgc3BgYGAgIkNsYXNzZXMgYW5kIE1ldGhvZHMgZm9yIFNwYXRpYWwgRGF0YSIsIGFuZCBgYGByZ2RhbGBgYCAiQmluZGluZ3MgZm9yIHRoZSAnR2Vvc3BhdGlhbCcgRGF0YSBBYnN0cmFjdGlvbiBMaWJyYXJ5IiBwcm92aWRlIGFjY2VzcyB0byBzcGF0aWFsIGRhdGEgYW5kIHNwYXRpYWwgZGF0YSBmb3JtYXRzIGZvciBhIG51bWJlciBvZiBvdGhlciBwYWNrYWdlcy4gSW4gb3JkZXIgdG8gcmVkdWNlIHRoZSBvdmVyYWxsIGNvbXBsZXhpdHkgb2YgdGhpcyBleGVyY2lzZSwgd2Ugd2lsbCBub3QgZGlzY3VzcyBhbGwgb2YgdGhlIGRhdGEgZm9ybWF0cyBvciBwYWNrYWdlcyB0aGF0IGNhbiBiZSB1c2VkIHRvIGFuYWx5emUgc3BhdGlhbCBkYXRhIGhlcmUuIEhvd2V2ZXIsIEkgd2lsbCBwcm92aWRlIGxpbmtzIHRvIHVzZWZ1bCBvbmxpbmUgcmVzb3VyY2VzIGluIHRoZSBleGVyY2lzZSBbUkVBRE1FXShodHRwczovL2dpdGh1Yi5jb20vY2hyaXNtZ2VudHJ5L01hcHBpbmctQmFzaWNzL2Jsb2IvbWFzdGVyL1JFQURNRS5tZCkgZG9jdW1lbnQuDQoNCiMjIFBhY2thZ2VzDQpGb3IgdGhpcyBleGVyY2lzZSB3ZSB3aWxsIHVzZSBhIGZldyBjb21tb24gcGFja2FnZXMgZm9yIHdvcmtpbmcgd2l0aCBzcGF0aWFsIGRhdGEuIE9uY2UgYWdhaW4sIG1hbnkgb2YgdGhlc2UgcGFja2FnZXMgd2lsbCByZXF1aXJlIGRlcGVuZGVuY2llcyBzbyBpbnN0YWxsaW5nIHRoZW0gbWF5IHRha2Ugc29tZSB0aW1lLg0KDQo8cCBhbGlnbj0iY2VudGVyIj4NCmBgYGdnc25gYGAgfCBgYGBsZWFmbGV0YGBgIHwgYGBgbWFwZGF0YWBgYCB8IGBgYG1hcHRvb2xzYGBgIHwgDQpgYGBPcGVuU3RyZWV0TWFwYGBgIHwgYGBgcmdkYWxgYGAgfCBgYGBzbW9vdGhyYGBgIHwgDQpgYGBzZmBgYCB8IGBgYHNwYGBgIHwgYGBgdGlkeXZlcnNlYGBgIHwgYGBgdG1hcGBgYA0KPC9wPg0KDQpJbiBvcmRlciB0byBhdm9pZCBmb3JjaW5nIHRoZXNlIHBhY2thZ2VzIHRvIHlvdXIgY29tcHV0ZXIsIHNlYXJjaCBpbiB0aGUgKlBhY2thZ2VzIFRhYiogb2YgKipSU3R1ZGlvKiogdG8gc2VlIGlmIHlvdSBuZWVkIHRvIGluc3RhbGwgdGhlbS4gT25jZSB5b3UgaGF2ZSBpbnN0YWxsZWQgdGhlbSB5b3UgY2FuIGNvbnRpbnVlIHdpdGggdGhlIHNjcmlwdCBiZWxvdzoNCmBgYHtyIFBhY2thZ2VzLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KcGFja2FnZXM8LWMoImdnc24iLCJsZWFmbGV0IiwibWFwZGF0YSIsIm1hcHRvb2xzIiwiT3BlblN0cmVldE1hcCIsInJnZGFsIiwic21vb3RociIsInNmIiwic3AiLCJ0aWR5dmVyc2UiLCJ0bWFwIikNCnNhcHBseShwYWNrYWdlcywgcmVxdWlyZSwgY2hhcmFjdGVyLm9ubHk9VCkNCmBgYA0KDQpUaGVzZSBwYWNrYWdlcyB3aWxsIHByb3ZpZGUgdGhlIG1pbmltdW0gZnVuY3Rpb25hbGl0eSByZXF1aXJlZCB0byBjb21wbGV0ZSB0aGlzIGV4ZXJjaXNlLiBBcyB5b3UgZXhwZXJpbWVudCB3aXRoIHZhcmlvdXMgc3R5bGVzIG9mIG1hcHBpbmcgeW91IG1heSByZXF1aXJlIGFkZGl0aW9uYWwgcGFja2FnZXMgdG8gYmUgaW5zdGFsbGVkIGF0IGEgbGF0ZXIgdGltZS4NCg0KIyBTaW1wbGUgTWFwcw0KVGhlcmUgYXJlIGEgbnVtYmVyIG9mIHZlcnkgc2ltcGxlIG1hcHMgeW91IGNhbiBjcmVhdGUgdGhhdCB1c2VzIGRhdGEgaW5jbHVkZWQgaW4gdGhlIHBhY2thZ2UgZG93bmxvYWRzLiBGb3IgdGhpcyBmaXJzdCBleGFtcGxlIHdlIGFyZSBnb2luZyB0byBjcmVhdGUgYSBtYXAgdGhhdCBkZXBpY3RzIHRoZSBsb2NhdGlvbiBvZiAqQXVzdGluIFBlYXkgU3RhdGUgVW5pdmVyc2l0eSouIFRvIGJlZ2luIHdlIG5lZWQgdG8gbG9hZCBzb21lIGRhdGEgZnJvbSBgYGBnZ3Bsb3QyYGBgIHVzaW5nIHRoZSBgYGBtYXBfZGF0YWBgYCBjb21tYW5kLg0KYGBge3IgU2ltcGxlIE1hcCBEYXRhLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0Kc3RhdGUgPC0gbWFwX2RhdGEoInN0YXRlIikNCmNvdW50eSA8LSBtYXBfZGF0YSgiY291bnR5IikNCmFwc3VfcG9pbnQgPC0gZGF0YS5mcmFtZSgieCIgPSAtODcuMzUzMDY5LCAieSIgPSAzNi41MzM2NTQpDQpgYGANCg0KVGhpcyBxdWljayBzY3JpcHQgbG9hZGVkIGFsbCBvZiB0aGUgc3RhdGUgYW5kIGNvdW50eSBpbmZvcm1hdGlvbiBmb3IgVVMgaW50byB0aGUgYXBwcm9wcmlhdGUgb2JqZWN0cyBhbmQgY3JlYXRlZCBhIHBvaW50IGNlbnRlcmVkIG9uIHRoZSBBUFNVIGNhbXB1cy4gSG93ZXZlciwgd2UgY2FuIGNyZWF0ZSBhIGJldHRlciBtYXAgYnkgZmlsdGVyaW5nIHRoZSBkYXRhIHRvIGZvY3VzIG9uIFRlbm5lc3NlZSBhbmQgTW9udGdvbWVyeSBDb3VudHkuDQpgYGB7ciBTaW1wbGUgTWFwIEZpbHRlcmVkIERhdGEsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp0biA8LSBjb3VudHkgJT4lIA0KICBmaWx0ZXIocmVnaW9uPT0idGVubmVzc2VlIikNCg0KbW9udGNvIDwtIGNvdW50eSAlPiUgDQogIGZpbHRlcihyZWdpb249PSJ0ZW5uZXNzZWUiKSAlPiUgDQogIGZpbHRlcihzdWJyZWdpb249PSJtb250Z29tZXJ5IikNCmBgYA0KDQpXaXRoIGp1c3QgdGhlc2Ugc2ltcGxlIGxpbmVzIG9mIGNvZGUgYW5kIGluY2x1ZGVkIGRhdGFzZXRzIHdlIGNhbiB1c2UgYGBgZ2dwbG90MmBgYCB0byBxdWlja2x5IGNyZWF0ZSBhIHNpbXBsZSBsb2NhdG9yIG1hcCBmb3IgY2FtcHVzLg0KYGBge3IgTG9jYXRvciBNYXAsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpnZ3Bsb3QoKSArIGdlb21fcG9seWdvbihkYXRhID0gc3RhdGUsIGFlcyh4PWxvbmcsIHkgPSBsYXQsIGdyb3VwID0gZ3JvdXApLA0KICAgICAgICAgICAgICAgICAgICAgICAgZmlsbCA9ICJ3aGl0ZSIsIGNvbG9yPSJibGFjayIpICsgDQogICAgICAgICAgIGdlb21fcG9seWdvbihkYXRhID0gdG4sIGFlcyh4PWxvbmcsIHkgPSBsYXQsIGdyb3VwID0gZ3JvdXApLA0KICAgICAgICAgICAgICAgICAgICAgICAgZmlsbCA9ICJncmF5IiwgY29sb3I9ImJsYWNrIikgKyANCiAgICAgICAgICAgZ2VvbV9wb2x5Z29uKGRhdGEgPSBtb250Y28sIGFlcyh4PWxvbmcsIHkgPSBsYXQsIGdyb3VwID0gZ3JvdXApLA0KICAgICAgICAgICAgICAgICAgICAgICAgZmlsbCA9ICJyZWQiLCBjb2xvcj0iYmxhY2siKSArIA0KICAgICAgICAgICBnZW9tX3BvaW50KGRhdGEgPSBhcHN1X3BvaW50LCBhZXMoeD14LHk9eSksIGNvbG9yPSJibGFjayIpICsNCiAgY29vcmRfZml4ZWQoeGxpbSA9IGMoLTkxLCAtODEpLCAgeWxpbSA9IGMoMzQsIDM3KSwgcmF0aW8gPSAxLjIpICsgDQogIHhsYWIoIkxvbmdpdHVkZSIpICsgeWxhYigiTGF0aXR1ZGUiKSArIGdndGl0bGUoIkFQU1UsIE1vbnRnb21lcnkgQ28uLCBUTiIpDQpgYGANCg0KS2VlcCBpbiBtaW5kLCB0aGF0IHdoZW4gdXNpbmcgYGBgZ2dwbG90MmBgYCB0aGUgb3JkZXIgb2YgdGhlIGBgYGdlb21zYGBgIGlzIGltcG9ydGFudCBmb3IgcHJvcGVyIGRpc3BsYXkgb2YgdGhlIGltYWdlLiBVc2luZyB0aGUgdmFyaW91cyBvcHRpb25zIHdpdGhpbiBgYGBnZ3Bsb3QyYGBgIHlvdSBjYW4gY3VzdG9taXplIHRoZSBtYXAgYmFzZWQgb24geW91ciBwcmVmZXJlbmNlcy4NCg0KIyMgTWFwcyB3aXRoIHJhc3RlciBpbWFnZXMNCkl0J3MgbGlrZWx5IHRoYXQgdGhlIG1hcCB5b3UgbmVlZCB0byBjcmVhdGUgaXMgc2xpZ2h0bHkgbW9yZSBjb21wbGV4IHRoYW4gdGhlIG9uZSBhYm92ZS4gTWF5YmUgeW91IGFyZSBpbnRlcmVzdGVkIGluIHVzaW5nIHNvbWUgc29ydCBvZiBpbWFnZXJ5IG9mIGJhc2VtYXAgdG8gYWRkIGNvbXBsZXhpdHkgb3IgcHJvdmlkZSBhZGRpdGlvbmFsIGluZm9ybWF0aW9uIGZvciBhIGxvY2F0aW9ucy4gSW4gdGhlIGRhdGEgZm9sZGVyIG9mIHRoaXMgZXhlcmNpc2UgaXMgYSBcKi5jc3YgZmlsZSBjYWxsZWQgKmNhbXB1c19wb2ludHMqLiBUaGlzIGZpbGUgY29udGFpbnMgdGhlIGxvY2F0aW9uIG9mIHNldmVyYWwgcG9pbnRzIG9uIHRoZSBtYWluIGNhbXB1cy4gSW4gb3JkZXIgdG8gdmlldyB0aGVzZSBmaWxlcyBvbiBhZXJpYWwgaW1hZ2VyeSBvZiBjYW1wdXMgd2UgYXJlIGdvaW5nIHRvIHVzZSBgYGBPcGVuU3RyZWV0TWFwYGBgIHRvIG9idGFpbiBhIGJhc2VtYXAgZm9yIG91ciBjbG9zZXIgbG9vayBhdCBjYW1wdXMuDQoNCj4gTm90ZTogQXMgb2YgMjAxOCwgR29vZ2xlIGJlZ2FuIHJlcXVpcmluZyBhbiBBUEkga2V5IGZvciB1c2Ugd2l0aCBgYGBnZXRfbWFwYGBgIHRoYXQgYWxsb3dlZCBmb3IgR29vZ2xlIGltYWdlcnkgdG8gYmUgdXNlZCBhcyBiYXNlbWFwcy4gQWx0aG91Z2ggeW91IGNhbiBvYnRhaW4gYSBjZXJ0YWluIG51bWJlciBvZiBtYXBzIGZyZWUgcGVyIG1vbnRoLCBJIGhhdmUgY2hvc2VuIHRvIGF2b2lkIHRoYXQgc2V0LXVwIGZvciB0aGlzIGV4YW1wbGUuDQoNClRvIHVzZSBgYGBPcGVuU3RyZWV0TWFwYGBgIHlvdSBuZWVkIHRvIGZpcnN0IG9idGFpbiB1cHBlci1sZWZ0IGFuZCBsb3dlci1yaWdodCBjb29yZGluYXRlcyBmb3IgdGhlIGxvY2F0aW9uIHlvdSBhcmUgaW50ZXJlc3RlZCBpbiBkaXNwbGF5aW5nLiBUaGlzIGNhbiBiZSBkb25lIHRocm91Z2ggcHJvZ3JhbXMgbGlrZSAqKkdvb2dsZSBFYXJ0aCoqIG9yIHRocm91Z2ggYGBgbWluKClgYGAgYW5kIGBgYG1heCgpYGBgIGNvbW1hbmRzIHVzaW5nIHRoZSBpbmZvcm1hdGlvbiBpbiB5b3VyIGRhdGFzZXQuIA0KYGBge3IgQ2FtcHVzIEltYWdlcnksIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpDYW1wdXNNYXAgPC0gb3Blbm1hcChjKDM2LjUzNjAsLTg3LjM1NzApLGMoMzYuNTMwMCwtODcuMzQ5NSksIHR5cGU9J2JpbmcnKQ0KDQpBUFNVIDwtIG9wZW5wcm9qKENhbXB1c01hcCwgcHJvamVjdGlvbiA9ICIrcHJvaj1sb25nbGF0ICtlbGxwcz1XR1M4NCArdW5pdHM9bSArbm9fZGVmcyIpDQpgYGANCg0KSW4gdGhlIHNjcmlwdCBhYm92ZSB0aGUgYm91bmRpbmcgYm94IHdhcyBpZGVudGlmaWVkLCB0aGUgdHlwZSBvZiBtYXAgbGlzdGVkICh2aWV3IGBgYD9vcGVubWFwYGBgIGZvciBhZGRpdGlvbmFsIG9wdGlvbnMpLCBhbmQgd2Ugc2V0IHRoZSBwcm9qZWN0aW9uIGZvciB0aGUgbWFwLiBSZWZlciB0byB0aGUgbGVjdHVyZSBmb3IgaW5mb3JtYXRpb24gcmVhcmRpbmcgdGhlIHByb2plY3Rpb24gYW5kIGVsbGlwc29pZC4NCg0KVG8gYWRkIHRoaXMgaW1hZ2VyeSB0byB0aGUgbWFwIHdlIG5lZWQgdG8gdXNlIHRoZSBgYGBhdXRvcGxvdGBgYCBmdW5jdGlvbiBpbiBgYGBnZ3Bsb3QyYGBgLiBUaGlzIGZ1bmN0aW9uIGV4dGVuZHMgdGhlIHBsb3R0aW5nIGZ1bmN0aW9ucyB0byBpbmNsdWRlIHNvbWUgc3BhdGlhbCBkYXRhIGNsYXNzZXMuIE90aGVyd2lzZSB0aGUgc2NyaXB0IGZvciBjcmVhdGluZyB0aGUgbWFwIGlzIHNpbWlsYXIgdG8gdGhlIHNpbXBsZSBtYXAgYWJvdmUuDQpgYGB7ciBTaW1wbGUgQ2FtcHVzIE1hcCwgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9MTAsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpjYW1wdXNfcG9pbnRzIDwtIHJlYWQuY3N2KGZpbGUgPSAiLi9EYXRhL2NhbXB1c19wb2ludHMuY3N2IikNCmF1dG9wbG90KEFQU1UpICsNCiAgZ2VvbV9wb2ludChkYXRhPWNhbXB1c19wb2ludHMsIGFlcyh4ID0gWCwgeSA9IFksIGNvbG9yPU5hbWUpLCBzaXplID0gNCwgYWxwaGEgPSAwLjgpICsNCiAgZ2VvbV90ZXh0KGRhdGE9Y2FtcHVzX3BvaW50cyxhZXMoeD1YLHk9WSxsYWJlbD1OYW1lKSwgY29sb3I9ImJsYWNrIiwgdmp1c3Q9LTAuNjAsIHNpemU9NC4wMSwgZm9udGZhY2U9ImJvbGQiKSArDQogIGdlb21fdGV4dChkYXRhPWNhbXB1c19wb2ludHMsYWVzKHg9WCx5PVksbGFiZWw9TmFtZSksIGNvbG9yPSJ3aGl0ZSIsIHZqdXN0PS0wLjc1LCBmb250ZmFjZT0iYm9sZCIpICsNCiAgbGFicyh4PSJMb25ndGl1ZGUiLCB5PSJMYXRpdHVkZSIpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQ0KYGBgDQoNCldpdGggdGhlIGFkZGl0aW9uIG9mIHRoZSBhZXJpYWwgcGhvdG8gdGhlcmUgaXMgYWRkaXRpb25hbCBzcGF0aWFsIGluZm9ybWF0aW9uIHRoYXQgY2FuIGJlIG9idGFpbmVkIGZyb20gdmlld2luZyB0aGUgbWFwLiBTaW1pbGFyIHRvIHRoZSBzaW1wbGUgbWFwIGFib3ZlLCB5b3UgY2FuIGN1c3RvbWl6ZSB0aGUgbWFwIHRocm91Z2ggc2V0dGluZyB2YXJpb3VzIHRoZW1lcyBhbmQgYWVzdGhldGljcyBpbiBgYGBnZ3Bsb3QyYGBgLiANCg0KIyMgVXNpbmcgS01McyBvciBTaGFwZWZpbGVzDQpPY2Nhc3Npb25hbGx5IHlvdSBtaWdodCByZWNlaXZlIGluZm9ybWF0aW9uIGZyb20gYSBjb2xsYWJvcmF0b3IgdGhhdCBpcyBub3QgYSBcKi5jc3YgZmlsZS4gRGVwZW5kaW5nIG9uIHRoZSBpbmRpdmlkdWFsLCB0aGF0IGluZm9ybWF0aW9uIG1pZ2h0IGJlIHByb3ZpZGVkIGFzIGEgXCoua21sIGZpbGUgZnJvbSAqKkdvb2dsZSBFYXJ0aCoqIG9yIGluIGFuICoqRVNSSSoqICpzaGFwZWZpbGUqLiBVc2luZyBhIHNpbXBsZSBpbXBvcnQgY29tbWFuZCBpbiB0aGUgYGBgcmdkYWxgYGAgcGFja2FnZSwgdGhlc2UgZmlsZXMgY2FuIGFsc28gYmUgdXNlZCB3aXRoIGBgYGdncGxvdDJgYGAuDQoNClRvIHJlYWQgZWl0aGVyIHNoYXBlZmlsZXMgb3IgS01MIGZpbGVzIHlvdSBjYW4gdXNlIHRoZSBmb2xsb3dpbmcgc3ludGF4Og0KYGBgDQpyZWFkT0dSKGRzbj0icGF0aCB0byB0aGUgc2hhcGVmaWxlIixsYXllcj0ibmFtZSBvZiB0aGUgc2hhcGVmaWxlIikNCg0Kb3INCg0KcmVhZE9HUigicGF0aCB0byBrbWwvbmFtZSBvZiB0aGUgZmlsZS5rbWwiKQ0KYGBgDQpXaXRoIHRoaXMgaW4gbWluZCwgeW91IGNhbiBlYXNpbHkgdXNlICoqR29vZ2xlIEVhcnRoKiogdG8gY3JlYXRlIHlvdXIgb3duIGRhdGFzZXRzIGFuZCBpbXBvcnQgdGhlbSBpbnRvICoqUioqIGZvciBzcGF0aWFsIGFuYWx5c2lzIGFuZCBtYXBwaW5nLiBUaGlzIGNhbiBiZSB1c2VmdWwgd2hlbiBjb2xsYWJvcmF0aW5nIHdpdGggaW5kaXZpZHVhbHMgb3Igd2l0aCBjaXRpemVuIHNjaWVuY2UgcHJvamVjdHMuDQoNCkluIHRoZSBkYXRhIGZvbGRlciBvZiB0aGlzIGV4ZXJjaXNlIHlvdSB3aWxsIGZpbmQgQ2FtcHVzX1BvaW50cy5rbWwgZmlsZSBhbmQgYSBNYWluX0NhbXB1cy5rbWwgZmlsZS4gVGhlIGNhbXB1cyBwb2ludHMgZmlsZSBpbmNsdWRlcyB0aGUgc2FtZSBkYXRhIGFzIHRoZSBwcmV2aW91cyBcKi5jc3YgZmlsZSBidXQgYXMgYSBrbWwsIGFuZCB0aGUgbWFpbiBjYW1wdXMgZmlsZSBjb250YWlucyBhIHBvbHlnb24gb3V0bGluZSBvZiB0aGUgbWFpbiBwb3J0aW9uIG9mIGNhbXB1cy4gWW91IGNhbiB1c2UgdGhlIHNjcmlwdCBhYm92ZSB0byBjcmVhdGUgZGF0YXNldHMgb3V0IG9mIHRob3NlIGZpbGVzLg0KDQpXb3JraW5nIGluIGBgYGdncGxvdDJgYGAgd2l0aCBTcGF0aWFsUG9pbnRzRGF0YUZyYW1lIG9yIFNwYXRpYWxQb2x5Z29uRGF0YUZyYW1lIGZpbGVzIGNhbiBiZSBkaWZmaWN1bHQgZHVlIHRvIHRoZSBzdHJ1Y3R1cmUgb2YgdGhlIGRhdGEgdHlwZXMuIFNvIHRvIGF2b2lkIGFkZGluZyBhZGRpdGlvbmFsIHBhY2thZ2VzIG9yIGRldGFpbGVkIGV4cGxhbmF0aW9uIGZvciB0aGlzIGV4ZXJjaXNlLCB3ZSB3aWxsIGNvbnZlcnQgdGhlIHBvaW50cyBhbmQgcG9seWdvbiB0byBhIGBgYGRhdGEuZnJhbWVgYGAgYW5kIGBgYHNwYXRpYWxwb2x5Z29uYGBgIHJlc3BlY3RpdmVseS4gSW4gbGF0ZXIgZXhlcmNpc2VzIHdlIHdpbGwgd29yayBkaXJlY3RseSB3aXRoIHRoZXNlIG1vcmUgZGlmZmljdWx0IHNwYXRpYWwgY2xhc3Nlcy4NCg0KVG8gY29udmVydCB0aGUgcG9pbnQga21sIHRvIGEgZGF0YS5mcmFtZToNCmBgYHtyIGNvbnZlcnQgcG9pbnQga21sLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KY2FtcHVzT0dSIDwtIHJlYWRPR1IoIi4vRGF0YS9DYW1wdXNfUG9pbnRzLmttbCIpDQpjYW1wdXNfcG9pbnRzIDwtIGNiaW5kKGNhbXB1c09HUkBkYXRhLGNhbXB1c09HUkBjb29yZHMpDQpjYW1wdXNfcG9pbnRzWzJdIDwtIE5VTEwNCmNhbXB1c19wb2ludHNbNF0gPC0gTlVMTA0KY29sbmFtZXMoY2FtcHVzX3BvaW50cykgPC0gYygiTmFtZSIsIlgiLCJZIikNCmBgYA0KVGhpcyBhbGxvd2VkIHVzIHRvIGltcG9ydCB0aGUga21sLCBjcmVhdGUgYSBkYXRhLmZyYW1lIG91dCBvZiB0aGUgY29vcmRpbmF0ZSBhbmQgbmFtZXMsIHJlbW92ZWQgdW5uZWNlc3NhcnkgY29sdW1ucywgYW5kIHJlbmFtZWQgdGhlIGNvbHVtbnMuDQoNClRvIGNvbnZlcnQgdGhlIHBvbHlnb24ga21sIHRvIGEgc3BhdGlhbCBwb2x5Z29uOg0KYGBge3IgY29udmVydCBwb2x5Z29uIGttbCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCm91dGxpbmVPR1IgPC0gcmVhZE9HUigiLi9EYXRhL01haW5fQ2FtcHVzLmttbCIpDQpvdXRsaW5lX3BvaW50cyA8LSBvdXRsaW5lT0dSQHBvbHlnb25zW1sxXV1AUG9seWdvbnNbWzFdXUBjb29yZHMNCmNvbG5hbWVzKG91dGxpbmVfcG9pbnRzKSA8LSBjKCJYIiwiWSIpDQpvdXRsaW5lIDwtIFBvbHlnb24ob3V0bGluZV9wb2ludHMpDQpzcF9vdXRsaW5lIDwtIFBvbHlnb25zKGxpc3Qob3V0bGluZSksMSkNCm91dGxpbmVfcG9seSA8LSBTcGF0aWFsUG9seWdvbnMobGlzdChzcF9vdXRsaW5lKSkNCnByb2o0c3RyaW5nKG91dGxpbmVfcG9seSkgPC0gQ1JTKCIrcHJvaj1sb25nbGF0ICtkYXR1bT1XR1M4NCArZWxscHM9V0dTODQiKQ0KYGBgDQpUaGlzIHNjcmlwdCBjcmVhdGVkIGEgc3BhdGlhbCBwb2x5Z29uIGFuZCBwcm9qZWN0ZWQgaXQgdG8gbWF0Y2ggdGhlIHBvaW50IGRhdGEuDQoNCk5vdyB3ZSBjYW4gdXNlIHRoZXNlIGZpbGVzIHRvIG1hcCB0aGUgZGF0YSBmcm9tIHRoZSBrbWwgZmlsZXMgc2ltaWxhciB0byB0aGUgcHJldmlvdXMgbWFwIG9mIHRoZSBjc3YgZGF0YS4NCmBgYHtyIGttbCBDYW1wdXMgTWFwLCBmaWcuaGVpZ2h0PTgsIGZpZy53aWR0aD0xMCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmF1dG9wbG90KEFQU1UpICsNCiAgZ2VvbV9wb2x5Z29uKGRhdGE9b3V0bGluZV9wb2x5LCBhZXMoeD1vdXRsaW5lX3BvbHlAcG9seWdvbnNbWzFdXUBQb2x5Z29uc1tbMV1dQGNvb3Jkc1ssMV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHk9b3V0bGluZV9wb2x5QHBvbHlnb25zW1sxXV1AUG9seWdvbnNbWzFdXUBjb29yZHNbLDJdKSwNCiAgICAgICAgICAgICAgIGFscGhhID0gLjUsIGNvbG9yPSJ3aGl0ZSIpICsNCiAgZ2VvbV9wb2ludChkYXRhPWNhbXB1c19wb2ludHMsIGFlcyh4ID0gWCwgeSA9IFksIGNvbG9yPU5hbWUpLCBzaXplID0gNCwgYWxwaGEgPSAwLjgpICsNCiAgZ2VvbV90ZXh0KGRhdGE9Y2FtcHVzX3BvaW50cyxhZXMoeD1YLHk9WSxsYWJlbD1OYW1lKSwgY29sb3I9ImJsYWNrIiwgdmp1c3Q9LTAuNjAsIHNpemU9NC4wMSwgZm9udGZhY2U9ImJvbGQiKSArDQogIGdlb21fdGV4dChkYXRhPWNhbXB1c19wb2ludHMsYWVzKHg9WCx5PVksbGFiZWw9TmFtZSksIGNvbG9yPSJ3aGl0ZSIsIHZqdXN0PS0wLjc1LCBmb250ZmFjZT0iYm9sZCIpICsNCiAgbGFicyh4PSJMb25ndGl1ZGUiLCB5PSJMYXRpdHVkZSIpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQ0KYGBgDQpUaGUgc2NyaXB0IGFib3ZlIGlzIGEgbGl0dGxlIG1vcmUgY29uZnVzaW5nIGJlY2FzZSBvZiB0aGUgc3BhdGlhbHBvbHlnb24gY2xhc3MgZGF0YS4gSW4gb3JkZXIgdG8gbWFwIHRoZSBhZXN0aGV0aWNzIHRvIHRoZSBwb2x5Z29uIHdlIG5lZWQgdG8gY29ubmVjdCB0byB0aGUgY29udGFpbmVycyB0aGF0IGhvbGQgdGhlIHggYW5kIHkgdmFsdWVzLiBUaGlzIGNhbiBiZSBhIGNvbmZ1c2luZyBzdHJpbmcsIGJ1dCB3ZSB3aWxsIGRpc2N1c3MgaXQgZnVydGhlciBpbiB1cGNvbWluZyBleGVyY2lzZXMuDQoNCiMjIFN0dWRlbnQgU3VibWl0dGVkIE1hcHBpbmcgUXVlc3Rpb24NCg0KPiBIb3cgdG8gY3JlYXRlIHNtb290aCBwb2x5Z29ucyBmb3Igc3BlY2llcyBkaXN0cmlidXRpb24gbWFwcyBmcm9tIGEgc2V0IG9mIHBvaW50cz8NCg0KRm9sbG93aW5nIHRoZSAicG9seWdvbiBrbWwgdG8gYSBzcGF0aWFsIHBvbHlnb24iIHNjcmlwdCBhYm92ZSwgaW5zdGFsbCBhbmQgbG9hZCB0aGUgYGBgc21vb3RocmBgYCBwYWNrYWdlIGlmIHlvdSBoYXZlbid0IGFscmVhZHkuIFRoZSBgYGBzbW9vdGhgYGAgY29tbWFuZCBpbiB0aGlzIHBhY2thZ2Ugd2lsbCB0YWtlIGEgcG9seWdvbiB3aXRoIGdlb21ldHJpYyBlZGdlcyBhbmQgInNtb290aCIgdGhlIGRhdGEgdXNpbmcgb25lIG9mIHRoZSBzbW9vdGhpbmcgbWV0aG9kcyBhdmFpbGFibGUgaW4gdGhlIHBhY2thZ2U6IGBgYGNoYWlraW5gYGAsIGBgYGtzbW9vdGhgYGAsIGBgYHNwbGluZWBgYCwgb3IgYGBgZGVuc2lmeWBgYC4gVGhlbiB5b3UgY2FuIGFkanVzdCB0aGUgc21vb3RobmVzcyB2YWx1ZSB0byBkZXRlcm1pbmUgaG93IG11Y2ggc21vb3RoaW5nIGlzIGFwcHJvcHJpYXRlLg0KDQpgYGB7ciBvcmlnaW5hbCBwb2x5Z29uLCBmaWcuaGVpZ2h0PTQsIGZpZy53aWR0aD02LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KI1Bsb3Qgb2YgdGhlIG9yaWdpbmFsIHBvbHlnb24NCnBsb3Qob3V0bGluZV9wb2x5KQ0KYGBgDQoNCmBgYHtyIHNtb290aGVkIHBvbHlnb24sIGZpZy5oZWlnaHQ9NCwgZmlnLndpZHRoPTYsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQojU21vb3RoZWQgdmVyc2lvbiBvZiB0aGUgcG9seWdvbg0Kc21vb3RoX291dGxpbmUgPC0gc21vb3RoKG91dGxpbmVfcG9seSwgbWV0aG9kPSJrc21vb3RoIiwgc21vb3RobmVzcyA9IDIpDQpwbG90KHNtb290aF9vdXRsaW5lLCBjb2wgPSBhbHBoYSgiZ3JleSIsIDAuMiksIGJvcmRlciA9ICJibGFjayIsIGx3ZCA9IDEuNSkNCmBgYA0KDQojIEludGVyYWN0aXZlIE1hcHMNCldoaWxlIHRoZXJlIGFyZSBhIG51bWJlciBvZiBwYWNrYWdlcyB0aGF0IGNhbiBoZWxwIGNyZWF0ZSBpbnRlcmFjdGl2ZSBtYXBzIHN1Y2ggYXMgYGBgcGxvdGx5YGBgIGFuZCBgYGBtYXB2aWV3YGBgLCBmb3IgdGhpcyBleGFtcGxlIHdlIGFyZSBnb2luZyB0byB1c2UgYGBgbGVhZmxldGBgYC4gKipMZWFmbGV0KiogYWxsb3dzIHlvdSB0byBjcmVhdGUgaW50ZXJhY3RpdmUgd2ViIG1hcHMgd2l0aCB0aGUgamF2YVNjcmlwdCAnTGVhZmxldCcgbGlicmFyeS4gQmVjYXVzZSB0aGlzIHBhY2thZ2UgYWxyZWFkeSBoYXMgaGFuZGxlcnMgZm9yIGBgYHNmYGBgIGNsYXNzIGRhdGEsIHdlIGNhbiB1c2UgdGhlIG9yaWdpbmFsIGRhdGEgd2UgaW1wb3J0ZWQgZnJvbSB0aGUga21sIGZpbGVzIHRvIGNvbXBsZXRlIHRoaXMgcG9ydGlvbi4NCg0KSW4gdGhlIG1vc3Qgc2ltcGxlIHZlcnNpb24sIHdlIHNldCB0aGUgcG9pbnQgZGF0YXNldCB3ZSB3YW50IHRvIHVzZSBmb3IgdGhlIG1hcCwgdGVsbCBgYGBsZWFmbGV0YGBgIHRoZSBzb3J0IG9mIG1hcmtlcnMgd2Ugd2FudCB0byB1c2UgdG8gaWRlbnRpZnkgdGhlIHBvaW50cywgYW5kIHRoZW4gbGV0IGBgYGxlYWZsZXRgYGAgb2J0YWluIHRoZSAqT1NNKiB0aWxlcyBmb3IgdGhlIGJhY2tncm91bmQgaW1hZ2UuDQpgYGB7ciBsZWFmbGV0IHNpbXBsZSwgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9OCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmxlYWZsZXQoY2FtcHVzT0dSKSAlPiUgDQogIGFkZFRpbGVzKCkgJT4lIA0KICBhZGRNYXJrZXJzKHBvcHVwID0gY2FtcHVzT0dSQGRhdGEkTmFtZSkNCmBgYA0KQ2xpY2tpbmcgb24gdGhlIG1hcmtlcnMgd2lsbCBpZGVudGlmeSB0aGUgcGxhY2UgYmFzZWQgb24gdGhlICJuYW1lIiB2YXJpYWJsZSBpbiBvdXIgZGF0YS4gQmVjYXVzZSB3ZSBoYXZlIGFkZGl0aW9uYWwgZGF0YSBmcm9tIHRoZSBwb2x5Z29uIGttbCB3ZSBjYW4gYWRkIHRvIHRoZSBtYXAgYW5kIGN1c3RvbWl6ZSBpdCBmdXJ0aGVyLg0KDQpgYGB7ciBwb2ludCBwb2x5Z29uIGxlYWZsZXQgbWFwLCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD04LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KbGVhZmxldChjYW1wdXNPR1IpICU+JSANCiAgYWRkVGlsZXMoKSAlPiUNCiAgYWRkUG9seWdvbnMob3V0bGluZU9HUiwgDQogICAgICAgICAgICAgIGxuZyA9IG91dGxpbmVPR1JAcG9seWdvbnNbWzFdXUBQb2x5Z29uc1tbMV1dQGNvb3Jkc1ssMV0sIA0KICAgICAgICAgICAgICBsYXQgPSBvdXRsaW5lT0dSQHBvbHlnb25zW1sxXV1AUG9seWdvbnNbWzFdXUBjb29yZHNbLDJdLA0KICAgICAgICAgICAgICBmaWxsQ29sb3IgPSAiZ3JleSIsDQogICAgICAgICAgICAgIGNvbG9yID0gImJsYWNrIikgJT4lDQogIGFkZENpcmNsZU1hcmtlcnMocG9wdXAgPSBjYW1wdXNPR1JAZGF0YSROYW1lLA0KICAgICAgICAgICAgICAgICAgIHdlaWdodCA9IDIsDQogICAgICAgICAgICAgICAgICAgY29sb3IgPSAiZ3JleSIsDQogICAgICAgICAgICAgICAgICAgZmlsbENvbG9yID0gInJlZCIsDQogICAgICAgICAgICAgICAgICAgZmlsbE9wYWNpdHkgPSAwLjcpDQpgYGANCkhlcmUgd2UgY3VzdG9taXplZCB0aGUgbWFwIHdpdGggQVBTVSBjb2xvcnMgYW5kIGFkZGVkIHRoZSBwb2x5Z29uIHRvIHRoZSBiYWNrZ3JvdW5kIHRvIGlkZW50aWZ5IHRoZSAibWFpbiIgY2FtcHVzIGFyZWEuIFdlIGNhbiB0YWtlIHRoaXMgYSBzdGVwIGZ1cnRoZXIgYW5kIHByb3ZpZGUgYWRkaXRpb25hbCBpbmZvcm1hdGlvbiBhYm91dCB0aGUgY2FtcHVzIGJ1aWxkaW5ncyBhbmQgcG9seWdvbiBieSBlZGl0aW5nIHRoZSBhdHRyaWJ1dGVzIG9mIGVhY2ggZGF0YXNldC4NCg0KYGBge3IgZWRpdCBhdHRyaWJ1dGVzIG9mIGxlYWZsZXQgbWFwLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0Kb3V0bGluZU9HUkBkYXRhJERlc2NyaXB0aW9uIDwtIGMoIkF1c3RpbiBQZWF5IFN0YXRlIFVuaXZlcnNpdHkiKQ0KDQpjYW1wdXNPR1JAZGF0YSREZXNjcmlwdGlvbiA8LSBjKCJBY2FkZW1pYyIsICJBZG1pbnN0cmF0aXZlIiwgIkFjYWRlbWljIiwgIkxpYnJhcnkiLCAiQWNhZGVtaWMiLCAiRm9vZCIsICJBZG1pbmlzdHJhdGl2ZSIsICJBY2FkZW1pYyIsICJSZXNpZGVudGlhbCIsICJBZG1pbmlzdHJhdGl2ZSIsICJBZG1pbmlzdHJhdGl2ZSIsICJCb29rc3RvcmUiLCAiQWNhZGVtaWMiLCAiQWNhZGVtaWMiLCAiUmVzaWRlbnRpYWwiLCAiQWNhZGVtaWMiLCAiQWNhZGVtaWMiLCAiQWNhZGVtaWMiLCAiQWNhZGVtaWMiLCAiUmVzaWRlbnRpYWwiLCAiUmVzaWRlbnRpYWwiLCAiQWRtaW5pc3RyYXRpdmUiLCAiQWNhZGVtaWMiKQ0KYGBgDQpJbiB0aGUgc2NyaXB0IGFib3ZlIHdlIHByb3ZpZGVkIGEgZGVzY3JpcHRpb24gb2YgdGhlIHBvbHlnb24gYW5kIGRlc2NyaXB0aW9ucyBvZiBlYWNoIGJ1aWxkaW5nIHRoYXQgY2FuIGJlIHVzZWQgdG8gaWRlbnRpZnkgdGhlIHB1cnBvc2Ugb2YgdGhlIGJ1aWxkaW5nIHdoZW4gdGhlIGN1cnNvciBob3ZlcnMgb3ZlciB0aGUgcG9pbnQuDQoNCmBgYHtyIGluZXJhY3RpdmUgbGVhZmxldCBtYXAsIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTgsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpsZWFmbGV0KGNhbXB1c09HUikgJT4lIA0KICBhZGRUaWxlcygpICU+JQ0KICBhZGRQb2x5Z29ucyhvdXRsaW5lT0dSLCANCiAgICAgICAgICAgICAgbG5nID0gb3V0bGluZU9HUkBwb2x5Z29uc1tbMV1dQFBvbHlnb25zW1sxXV1AY29vcmRzWywxXSwgDQogICAgICAgICAgICAgIGxhdCA9IG91dGxpbmVPR1JAcG9seWdvbnNbWzFdXUBQb2x5Z29uc1tbMV1dQGNvb3Jkc1ssMl0sDQogICAgICAgICAgICAgIGxhYmVsID0gb3V0bGluZU9HUkBkYXRhJERlc2NyaXB0aW9uLA0KICAgICAgICAgICAgICBmaWxsQ29sb3IgPSAiZ3JleSIsDQogICAgICAgICAgICAgIGNvbG9yID0gImJsYWNrIikgJT4lDQogIGFkZENpcmNsZU1hcmtlcnMocG9wdXAgPSBjYW1wdXNPR1JAZGF0YSROYW1lLA0KICAgICAgICAgICAgICAgICAgIGxhYmVsID0gY2FtcHVzT0dSQGRhdGEkRGVzY3JpcHRpb24sDQogICAgICAgICAgICAgICAgICAgd2VpZ2h0ID0gMiwNCiAgICAgICAgICAgICAgICAgICBjb2xvciA9ICJncmV5IiwNCiAgICAgICAgICAgICAgICAgICBmaWxsQ29sb3IgPSAicmVkIiwNCiAgICAgICAgICAgICAgICAgICBmaWxsT3BhY2l0eSA9IDAuNykNCmBgYA0KTm90aWNlIG5vdyB0aGF0IHdoZW4geW91IG1vdmUgdGhlIGN1cnNvciBhcm91bmQgdGhlcmUgaXMgYWRkaXRpb25hbCBpbmZvcm1hdGlvbiBwcm92aWRlZCBhYm91dCBlYWNoIGRhdGFzZXQuIFRoaXMgY2FuIGJlIHVzZWQgd2l0aCBudW1iZXJvdXMgdmFyaWFibGUgb3IgdG8gY3JlYXRlIGFuIGF0dHJpYnV0ZSB0YWJsZSBmb3IgZWFjaCBwb2ludCB3aGVuIGNsaWNrZWQuDQoNCiMjIFRlcnJhaW4gYW5kIEVsZXZhdGlvbg0KDQoqKkxlYWZsZXQqKiBoYXMgYSBudW1iZXIgb2YgcG9zc2libGUgYmFja2dyb3VuZHMgdGhhdCBjYW4gYmUgaW5jbHVkZWQgYXMgYSBiYXNlbWFwIHRvIHlvdXIgcHJvamVjdC4gRm9yIGV4YW1wbGUsIGlmIHlvdSB3ZXJlIGludGVyZXN0ZWQgaW4gZWxldmF0aW9uIHlvdSBjYW4gY29ubmVjdCB0byBhIHdlYi1tYXBwaW5nIHNlcnZpY2UgdG8gb2J0YWluIGEgem9vbWFibGUgdGVycmFpbiBsYXllcg0KDQpgYGB7ciBpbmVyYWN0aXZlIHppb24gbWFwLCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD04LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KbGVhZmxldCgpICU+JSANCiAgc2V0VmlldyhsbmcgPSAtMTEyLjk1NjQ3MiwgbGF0ID0gMzcuMjUxMzQzLCB6b29tID0gMTMpICU+JQ0KICBhZGRXTVNUaWxlcygiaHR0cHM6Ly9iYXNlbWFwLm5hdGlvbmFsbWFwLmdvdi9hcmNnaXMvc2VydmljZXMvVVNHU1RvcG8vTWFwU2VydmVyL1dtc1NlcnZlciIsIGxheWVycyA9ICIwIikgJT4lDQogIGFkZE1pbmlNYXAoem9vbUxldmVsT2Zmc2V0ID0gLTQpICU+JQ0KICBhZGRTY2FsZUJhcigpDQpgYGANCg0KIyMgU2VsZWN0YWJsZSBMYXllcnMNCg0KQWRkaXRpb25hbGx5LCB5b3UgY2FuIGluY29ycG9yYXRlIHNldmVyYWwgYmFzZW1hcHMgYW5kIGFkZCBhbiBpbnRlcmFjdGl2ZSBzZWxlY3Rpb24gdG9vbCB0byBjaG9zZSB3aGF0IGJhY2tncm91bmQgdG8gZGlzcGxheSwgc2V0IHRyYW5wYXJlbmNpZXMgZm9yIG11bHRpcGxlIGJhY2tncm91bmRzIHRvIGJlIGRpc3BsYXllZCBhdCBvbmNlLCBoYXZlIGxpdmUgdXBkYXRlcyBzdHJlYW1lZCB0byB5b3VyIG1hcCwgb3IgdG9nZ2xlIHRoZSBkYXRhIG9mZiBhbmQgb24uDQoNCmBgYHtyIG11bHRpIGJhY2tncm91bmQgbWFwLCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD04LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KbGVhZmxldChjYW1wdXNPR1IpICU+JSANCiAgYWRkVGlsZXMoZ3JvdXAgPSAiT1NNIiklPiUNCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkQ2FydG9EQi5Qb3NpdHJvbiwgZ3JvdXAgPSAiQ2FydG9EQiIpICU+JQ0KICAjYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkRXNyaS5OYXRHZW9Xb3JsZE1hcCkgJT4lDQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJEVzcmkuV29ybGRJbWFnZXJ5LCBncm91cCA9ICJFU1JJIikgJT4lDQogICNhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRTdGFtZW4uVG9uZXIpICU+JQ0KICAgIHNldFZpZXcobG5nID0gLTg3LjM1MzA2OSwgbGF0ID0gMzYuNTMzNjU0LCB6b29tID0gMTcpICU+JQ0KICBhZGRQb2x5Z29ucyhvdXRsaW5lT0dSLCANCiAgICAgICAgICAgICAgbG5nID0gb3V0bGluZU9HUkBwb2x5Z29uc1tbMV1dQFBvbHlnb25zW1sxXV1AY29vcmRzWywxXSwgDQogICAgICAgICAgICAgIGxhdCA9IG91dGxpbmVPR1JAcG9seWdvbnNbWzFdXUBQb2x5Z29uc1tbMV1dQGNvb3Jkc1ssMl0sDQogICAgICAgICAgICAgIGxhYmVsID0gb3V0bGluZU9HUkBkYXRhJERlc2NyaXB0aW9uLA0KICAgICAgICAgICAgICBmaWxsQ29sb3IgPSAiZ3JleSIsDQogICAgICAgICAgICAgIGNvbG9yID0gImJsYWNrIikgJT4lDQogIGFkZENpcmNsZU1hcmtlcnMocG9wdXAgPSBjYW1wdXNPR1JAZGF0YSROYW1lLA0KICAgICAgICAgICAgICAgICAgIGxhYmVsID0gY2FtcHVzT0dSQGRhdGEkRGVzY3JpcHRpb24sDQogICAgICAgICAgICAgICAgICAgZ3JvdXAgPSAiQVBTVSIsDQogICAgICAgICAgICAgICAgICAgd2VpZ2h0ID0gMiwNCiAgICAgICAgICAgICAgICAgICBjb2xvciA9ICJncmV5IiwNCiAgICAgICAgICAgICAgICAgICBmaWxsQ29sb3IgPSAicmVkIiwNCiAgICAgICAgICAgICAgICAgICBmaWxsT3BhY2l0eSA9IDAuNykgJT4lDQogIGFkZExheWVyc0NvbnRyb2woDQogICAgYmFzZUdyb3VwcyA9IGMoIk9TTSIsICJFU1JJIiwgIkNhcnRvREIiKSwNCiAgICBvcHRpb25zID0gbGF5ZXJzQ29udHJvbE9wdGlvbnMoY29sbGFwc2VkID0gRkFMU0UpLA0KICAgIG92ZXJsYXlHcm91cHMgPSAiQVBTVSIpDQpgYGANCg0KVGhpcyBpcyBvbmx5IGEgdmVyeSBzbWFsbCBmcmFjdGlvbiBvZiB0aGUgcG9zc2libGUgYXBwbGljYXRpb25zIGZvciBtYXBwaW5nIHRoYXQgY2FuIGJlIGRvbmUgaW4gKipSKiouDQoNCiMgWW91ciBUdXJuIQ0KVXNpbmcgdGhlIHNraWxscyBkaXNjdXNzZWQgaW4gdGhpcyBleGVyY2lzZSwgY3JlYXRlIGEgc2l0ZSBtYXAgZm9yIHlvdXIgdGhlc2lzIHByb2plY3Qgb3Igb3RoZXIgZGF0YXNldCBvZiBpbnRlcmVzdC4gVGhpcyBtYXAgY2FuIGJlIHN0YXRpYyBvciBpbnRlcmFjdGl2ZSwgYW5kIGNhbiBjb250YWluIGFzIG1hbnkgZGF0YXNldHMgb3IgZmVhdHVyZXMgYXMgeW91IGZlZWwgYXJlIG5lY2Vzc2FyeSB0byBwcm9wZXJseSBkaXNwbGF5IHlvdXIgZGF0YS4gVGhpcyBtYXAgc2hvdWxkIGJlIGFkZGVkIHRvIHRoZSBwcm9qZWN0IHBhZ2UgeW91IGNyZWF0ZWQgaW4gdGhlIGxhc3QgZXhlcmNpc2UgdG8gaGVscCBidWlsZCBhICJzaXRlIGRlc2NyaXB0aW9uIiBzZWN0aW9uIGFuZC9vciAicmVzdWx0cyIgc2VjdGlvbiBpZiB0aGF0IGRhdGEgaXMgY3VycmVudGx5IGF2YWlsYWJsZS4gUmVtZW1iZXIgdGhhdCB0aGlzIHBhZ2Ugc2hvdWxkIGJlIGNvbnNpc3RlbnRseSB1cGRhdGVkIHRocm91Z2hvdXQgdGhlIHJlbWFpbmRlciBvZiB0aGUgc2VtZXN0ZXIgY3VsbWluYXRpbmcgaW4geW91ciBmaW5hbCBwcmVzZW50YXRpb24gdG8gdGhlIGNsYXNzIGluIERlY2VtYmVyLiBGb3IgYW4gYWRkZWQgY2hhbGxlbmdlLCB5b3UgY2FuIHRyeSB0byBmb3JrIHRoaXMgcmVwb3NpdG9yeSB0byBnaXZlIHlvdSBhY2Nlc3MgdG8gdGhlIGRhdGEgZmlsZXMgYW5kIGEganVtcHN0YXJ0IG9uIHlvdXIgc2NyaXB0Lg==